/*
 * Copyright (C) 2009 Leandro Aparecido <lehphyro@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.metalframework.persistence.interceptor;

import java.lang.reflect.*;

import org.metalframework.persistence.engine.*;


/**
 * Base class for AOP to facilitate aspect implementation.
 * <p>The aspect should be of type "around" method invocation and the sequence of invocations should
 * be like the following:
 * <pre class="code">
 * Object[] modifiedMethodArguments = beforeCall(target, method, methodArguments);
 * --> Call intercepted method with modifiedMethodArguments
 * return afterCall(valueReturnedByMethodCall)
 * </pre>
 * @since 1.0
 * @see org.metalframework.persistence.interceptor.autopersist.AutomaticPersistenceInterceptor
 * @see org.metalframework.persistence.engine.JapaObject
 */
public abstract class JapaAspectSupport {
	/**
	 * Stores the current method call context.
	 */
	private static ThreadLocal<MethodCallContext> localContext = new ThreadLocal<MethodCallContext>();

	/**
	 * Japa engine to be used by the aspect.
	 */
	private JapaEngine japaEngine;

	/**
	 * Must be called before method execution to apply automatic persistence processing.
	 * The method {@link JapaAspectSupport#getJapaObjectFor(Object)} is called to access
	 * the JapaObject of the target of the method invocation.
	 * 
	 * @param target Target object of the method invocation.
	 * @param method Method being called.
	 * @param arguments Arguments of the method call.
	 * @return Modified arguments with automatic persistence processing applied.
	 * @see #createMethodCallContext(Object, Method, Object...)
	 * @see #getJapaObjectFor(Object)
	 * @see JapaEngine#processMethodCall(JapaObject, Method, Object...)
	 */
	protected Object[] beforeCall(Object target, Method method, Object... arguments) {
		MethodCallContext context = createMethodCallContext(target, method, arguments);
		localContext.set(context);
		
		return context.getModifiedArguments();
	}

	/**
	 * Must be called after method execution to apply automatic persistence processing.
	 * 
	 * @param returnValue Value returned by method execution.
	 * @return Modified return with automatic persistence processing applied.
	 * @throws Exception If some persistence or runtime error occurs.
	 * @see JapaEngine#processMethodReturn(MethodCallContext, Object)
	 */
	protected Object afterCall(Object returnValue) throws Throwable {
		MethodCallContext context = localContext.get();
		localContext.remove();
		if (context == null) {
			throw new IllegalStateException("Method call context not found, did you call onBeforeCall?");
		}
		return getJapaEngine().processMethodReturn(context, returnValue);
	}
	
	/**
	 * Creates the method call context with necessary data parameters.
	 * 
	 * @param target Target object of the method invocation.
	 * @param method Method being called.
	 * @param arguments Arguments of the method call.
	 * @return Method call context.
	 * @see #getJapaObjectFor(Object)
	 * @see JapaEngine#processMethodCall(JapaObject, Method, Object...)
	 */
	protected MethodCallContext createMethodCallContext(Object target, Method method, Object... arguments) {
		return getJapaEngine().processMethodCall(getJapaObjectFor(target), method, arguments);
	}

	/**
	 * Must return the {@link JapaObject} associated with the parameter.
	 * If no JapaObject is returned, functionalities that rely upon JapaObject will not be
	 * available, for example, persistent collection flushing.
	 * 
	 * @param obj Object to retrieve JapaObject.
	 * @return JapaObject for the object or null if unavailable.
	 * @see org.metalframework.persistence.engine.JapaObject
	 */
	protected abstract JapaObject getJapaObjectFor(Object obj);
	
// Getters/Setters
	
	public JapaEngine getJapaEngine() {
		return japaEngine;
	}
	
	public void setJapaEngine(JapaEngine japaEngine) {
		this.japaEngine = japaEngine;
	}
}
